﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Infrastructure.Annotations;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TanmiaGrp.Basis.Core;
using TanmiaGrp.GeneralLedger.Core;

namespace TanmiaGrp.GeneralLedger.SqlServerRepository
{
    public class GLContext : DbContext
    {
        public static readonly string IX_AccountName = "IX_Account_AccountName";
        public static readonly string IX_AccountNumber = "IX_Account_AccountNumber";

        internal ObjectStateManager ObjectStateManager
        {
            get
            {
                return ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
            }
        }

        public DbSet<Core.Group> Groups { get; set; }
        public DbSet<Core.Account> Accounts { get; set; }
        public DbSet<Core.Journal> Journals { get; set; }
        public DbSet<Core.Voucher> Vouchers { get; set; }

        public GLContext()
            : base("CnnStr")
        {
            this.Configuration.LazyLoadingEnabled = false;
            this.Configuration.ProxyCreationEnabled = false;
            this.Configuration.ValidateOnSaveEnabled = false;

            // MUST: Because the underlyning EF return the ICollection as List
            // and we need it ObservableCollection
            ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += ObjectContext_ObjectMaterialized;
        }

        #region Helper Methods

        private void ObjectContext_ObjectMaterialized(object sender, System.Data.Entity.Core.Objects.ObjectMaterializedEventArgs e)
        {
            foreach (PropertyInfo prop in e.Entity.GetType().GetProperties().Where(x => x.GetIndexParameters().Count() == 0))
            {
                DbCollectionEntry collectionEntry = Entry(e.Entity).Member(prop.Name) as DbCollectionEntry;
                if (collectionEntry != null)
                {
                    Type elementType = TryGetElementType(prop);
                    if (elementType != null)
                    {
                        Type collectionType = typeof(BusinessObjectCollection<>).MakeGenericType(elementType);
                        collectionEntry.CurrentValue = Activator.CreateInstance(collectionType);
                    }
                }
            }
        }

        private Type TryGetElementType(PropertyInfo collectionProperty)
        {
            // We can only replace properties that are declared as ICollection<T>
            // and have a setter.
            var propertyType = collectionProperty.PropertyType;
            if (propertyType.IsGenericType &&
                propertyType.GetGenericTypeDefinition() == typeof(ICollection<>) &&
                collectionProperty.GetSetMethod() != null)
            {
                return propertyType.GetGenericArguments().Single();
            }
            return null;
        }

        public IEnumerable<string> KeysFor(Type entityType)
        {
            Contract.Requires(entityType != null);

            entityType = ObjectContext.GetObjectType(entityType);

            var metadataWorkspace =
                ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;
            var objectItemCollection =
                (ObjectItemCollection)metadataWorkspace.GetItemCollection(DataSpace.OSpace);

            var ospaceType = metadataWorkspace
                .GetItems<EntityType>(DataSpace.OSpace)
                .SingleOrDefault(t => objectItemCollection.GetClrType(t) == entityType);

            if (ospaceType == null)
            {
                throw new ArgumentException(
                    string.Format(
                        "The type '{0}' is not mapped as an entity type.",
                        entityType.Name),
                    "entityType");
            }

            return ospaceType.KeyMembers.Select(k => k.Name);
        }

        public object[] KeyValuesFor(object entity)
        {
            Contract.Requires(entity != null);

            var entry = this.Entry(entity);
            return this.KeysFor(entity.GetType())
                .Select(k => entry.Property(k).CurrentValue)
                .ToArray();
        }
        #endregion

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

            modelBuilder.Ignore<System.ComponentModel.DataAnnotations.ValidationResult>();

            modelBuilder.Entity<Group>()
                .Ignore(x => x.IsValid)
                .Ignore(x => x.IsSelected);

            modelBuilder.Entity<Account>()
                .Ignore(x => x.IsValid)
                .Ignore(x => x.IsSelected)
                .Ignore(x => x.IsExpanded);

            modelBuilder.Entity<Account>()
                .Property(x => x.AccountName)
                .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute(IX_AccountName) { IsUnique = true }));

            modelBuilder.Entity<Account>()
                .Property(x => x.AccountNumber)
                .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute(IX_AccountNumber) { IsUnique = true }));

            modelBuilder.Entity<Account>()
                .HasMany<Group>(np => np.AccountGroups)
                .WithMany()
                .Map(x =>
                {
                    x.MapLeftKey("AccountID");
                    x.MapRightKey("GroupID");
                    x.ToTable("AccountGroup");
                });

            modelBuilder.Entity<Account>()
                .HasOptional<Account>(np => np.Parent)
                .WithMany(np => np.AccountChildren)
                .HasForeignKey(fk => fk.ParentAccountID);

            modelBuilder.Entity<Account>()
                .HasOptional<Account>(np => np.FinalAccount)
                .WithMany()
                .HasForeignKey(fk => fk.FinalAccountID);

            modelBuilder.Entity<Journal>()
                .Ignore(x => x.IsValid)
                .Ignore(x => x.IsSelected);

            modelBuilder.Entity<JournalEntry>()
                .Ignore(x => x.IsValid)
                .Ignore(x => x.IsSelected);

            modelBuilder.Entity<Voucher>()
                .Ignore(x => x.IsValid)
                .Ignore(x => x.IsSelected);

            modelBuilder.Entity<VoucherDetail>()
                .Ignore(x => x.IsValid)
                .Ignore(x => x.IsSelected)
                .Ignore(x => x.Balance)
                .Ignore(x => x.RunningBalance);

            base.OnModelCreating(modelBuilder);
        }
    }
}
